<template>
  <div class="page">
    <div class="tableBac" v-for="item in allTable" :key="item.name">
      <p class="pageTitle">
        {{ item.name }} ({{ item.tableData.length }}):
      </p>
      <el-table 
        :height="500" 
        :header-cell-style="{ 'background-color': '#f5f6fa', color: '#6b6b6d'}"
        :data="item.tableData" tooltip-effect="light">
        <el-table-column :resizable="false" :prop="itemB.english" :label="itemB.chinese" v-for="itemB in item.headData" />
        <el-table-column fixed="right" align="right" label="操作" width="120" v-if="item.typeEnglishName === 'error'">
          <template #default="scope">
            <el-button link type="primary" size="small" @click="checkCode(scope.row)">查看源码</el-button>
            <el-button link type="primary" size="small" @click="lookBack(scope.row)" v-if="scope.row.isHaveEvents">回看</el-button>
          </template>
        </el-table-column>
      </el-table>
    </div>
    <el-dialog title="查看录屏:" v-model="myDialogShow" width="1064px" :close-on-click-modal="false" class="dialogBox">
      <div style="width: 100%;display: flex;">
        <div id="player" style="width: 100%;"></div>
      </div>
    </el-dialog>
    <el-dialog title="源码:" v-model="myDialogShowB" width="1064px" :close-on-click-modal="false" class="dialogBox">
      <div class="pageTop">
        <p>页面地址: {{ errorObj.pagePath }}</p>
        <p style="margin-top: 10px;">报错信息: {{ errorObj.message }}</p>
      </div>
      <div class="codeContentBox">
        <div class="codeContentBox_left" v-if="errorObj.lineNumber && errorObj.lineNumber > 10">
          <p v-for="item in 21" :style="{color: errorObj.codeNumber+item === errorObj.lineNumber ? '#ff5d3e' : ''}">{{ errorObj.codeNumber+item }}</p>
        </div>
        <div class="codeContentBox_left" v-else>
          <p v-for="item in errorObj.codeNumber" :style="{color: item === errorObj.lineNumber ? '#ff5d3e' : ''}">{{ item }}</p>
        </div>
        <div v-html="errorObj.codeContent" class="codeContentBox_right"></div>
      </div>
    </el-dialog>
  </div>
</template>
  
<script lang="ts" setup>
import { onMounted, ref, getCurrentInstance } from 'vue'
import service from '@/utils/request'
import { isGetRadio, getSourceMap } from '@/api/home'
import rrwebPlayer from 'rrweb-player';
import { unzlibSync, strToU8, strFromU8 } from 'fflate'
const dayjs = (getCurrentInstance()!.proxy as any).dayjs
import 'rrweb-player/dist/style.css';
import sourceMap from 'source-map-js';
import { ElMessage } from 'element-plus'
interface AllTable {
  name: string
  typeEnglishName: string
  tableData: any
  headData: Array<{ english: string, chinese: string }>
}
// 显示到视图层的初始数据:
let timeID:any = null
const params = [
  {
    name: '点击事件触发',
    typeEnglishName: 'clickEvent',
    tableData: [],
    headData: [
      { english: 'currentTime', chinese: '上报时间' },
      { english: 'describe', chinese: '按钮描述' },
      { english: 'pageTitle', chinese: '当前页面标题' },
      { english: 'pageUrl', chinese: '当前页面地址' },
    ]
  },
  {
    name: '接口请求时长',
    typeEnglishName: 'requestTime',
    tableData: [],
    headData: [
      { english: 'currentTime', chinese: '上报时间' },
      { english: 'method', chinese: '请求方式' },
      { english: 'url', chinese: '请求URL' },
      { english: 'describe', chinese: '时长' },
      { english: 'pageTitle', chinese: '当前页面标题' },
      { english: 'pageUrl', chinese: '当前页面地址' },
      { english: 'status', chinese: '请求状态' },
    ]
  },
  {
    name: '页面停留时长',
    typeEnglishName: 'pageStayTime',
    tableData: [],
    headData: [
      { english: 'currentTime', chinese: '上报时间' },
      { english: 'describe', chinese: '时长' },
      { english: 'pageTitle', chinese: '页面标题' },
      { english: 'pageUrl', chinese: '页面地址' },
    ]
  },
  {
    name: '自定义链路埋点',
    typeEnglishName: 'clickToRequest_time',
    tableData: [],
    headData: [
      { english: 'currentTime', chinese: '上报时间' },
      { english: 'describe', chinese: '间隔时长' },
      { english: 'remark', chinese: '备注' },
      { english: 'pageTitle', chinese: '当前页面标题' },
      { english: 'pageUrl', chinese: '当前页面地址' },
    ]
  },
  {
    name: '页面加载',
    typeEnglishName: 'navigationTime',
    tableData: [],
    headData: [
      { english: 'currentTime', chinese: '上报时间' },
      { english: 'DNS', chinese: '域名查询时间' },
      { english: 'TCP', chinese: '创建传输通讯协议时间(TCP)' },
      { english: 'SSL', chinese: 'SSL握手时间' },
      { english: 'domTree', chinese: '解析dom树耗时' },
      { english: 'domready', chinese: 'dom准备时间' },
      { english: 'firstRender', chinese: '首次渲染时间' },
      { english: 'pageTitle', chinese: '当前页面标题' },
      { english: 'name', chinese: '资源页面地址' },
      { english: 'request', chinese: '请求耗时' },
      { english: 'duration', chinese: '总下载时间' },
      { english: 'whiteScreenTime', chinese: '白屏时间' }
    ]
  },
  {
    name: '静态资源加载',
    typeEnglishName: 'resourceTime',
    tableData: [],
    headData: [
      { english: 'currentTime', chinese: '上报时间' },
      { english: 'DNS', chinese: '域名查询时间' },
      { english: 'TCP', chinese: '创建传输通讯协议时间(TCP)' },
      { english: 'SSL', chinese: 'SSL握手时间' },
      { english: 'pageTitle', chinese: '当前页面标题' },
      { english: 'name', chinese: '资源名称' },
      { english: 'pageUrl', chinese: '当前页面地址' },
      { english: 'duration', chinese: '总下载时间' },
      { english: 'TTFB', chinese: 'TTFB(首包时间)' },
      { english: 'response', chinese: '响应时间(剩余包时间)' }
    ]
  },
  {
    name: '页面报错信息',
    typeEnglishName: 'error',
    tableData: [],
    headData: [
      { english: 'currentTime', chinese: '上报时间' },
      { english: 'errorType', chinese: '错误类型' },
      { english: 'pageTitle', chinese: '页面标题' },
      { english: 'pageUrl', chinese: '页面地址' },
      { english: 'message', chinese: '报错信息' },
      { english: 'fileName', chinese: '文件' },
      { english: 'lineNumber', chinese: '行' },
      { english: 'columnNumber', chinese: '列' }
    ]
  }
]
const allTable = ref<Array<AllTable>>(JSON.parse(JSON.stringify(params)))
let ws: any = null
const reader = new FileReader();
const myDialogShow = ref(false)
const myDialogShowB = ref(false)
const errorObj = ref({
  pagePath:'',
  codeNumber:0,
  lineNumber:0,  // 报错的行
  codeContent:'',
  message:''
})

// 页面一打开就调用:
onMounted(() => {
  connectSocket()
})
// 注册事件:
// 查看代码所在位置
const checkCode = async (row:any) =>{
  const  { columnNumber, lineNumber ,fileName, message, entryName } = row
  myDialogShowB.value = true
  errorObj.value.message = message
  if(~fileName.lastIndexOf('.html')){
    // 兼容传统多页面项目
    analysisTraditionPage(fileName,lineNumber)
  }else{
    // 单页面项目
    const resA = await getSourceMap({ fileName,entryName }).catch(()=>{})
    reader.readAsText(resA)
    reader.onload = async function () {
      const data = JSON.parse(reader.result)
      if(!data.result){
        // sources:有关联的所有页面url
        // sourcesContent:所有页面的内容
        const { sources,sourcesContent } = data
        console.log(sources,sourcesContent,'sources,sourcesContent')
        try {
          let consumer = await new sourceMap.SourceMapConsumer(data)
          const res = consumer.originalPositionFor({line: Number(lineNumber),column: Number(columnNumber)})
          errorObj.value.pagePath = res.source.replace('webpack:///', '')  // 页面具体地址
          const index = sources.indexOf(res.source)
          let pageContent = sourcesContent[index]
          codeContentFunc(pageContent,res.line)
        } catch (error) {
          ElMessage({message: '源码解析失败',type: 'warning'})
        }
      }else{
        analysisTraditionPage(fileName,lineNumber)
      }
    }
  }
}
// 解析传统的源码文件
const analysisTraditionPage = async(fileName:string,lineNumber:string) =>{
  const resA = await service.get(fileName).catch(()=>{})
    if(resA){
      errorObj.value.pagePath = fileName
      codeContentFunc(JSON.parse(JSON.stringify(resA)),Number(lineNumber))
    }
}
// 解析源码文件成数组,然后显示到页面上
const codeContentFunc = (pageStr:string,lineNumber:number) =>{
  const pageContentArr = pageStr.split('\n')
  console.log(pageContentArr.length,'pageContentArr')
  console.log(lineNumber,'pageContentArr')
  // \n :换行
  // \s :空格
  for (let i = 0; i < pageContentArr.length; i++) {
    let item = pageContentArr[i].replace(/"/g,"\'")  // 大小必须错开,可是又不知道里边内容是大
    if(i === lineNumber-1){
      pageContentArr[i] = `<input class=inputBoxB type='text' value="${item}">`
    }else{
      pageContentArr[i] = `<input class=inputBox type='text' value="${item}">`
    }
  }
  errorObj.value.lineNumber = lineNumber
  if(lineNumber > 10){
    errorObj.value.codeContent = pageContentArr.slice(lineNumber-1-10,lineNumber-1+11).join('')
    errorObj.value.codeNumber = lineNumber-1-10
  }else{
    errorObj.value.codeContent = pageContentArr.join('')
    errorObj.value.codeNumber = pageContentArr.length
  }
}
// 回看,将记录的变更按照对应的时间一一重放
const lookBack = async(row:any) =>{
  const playerBox = document.getElementById('player') as HTMLDivElement
  if(playerBox){ playerBox.innerHTML = '' }
  myDialogShow.value = true
  const data = {  
    columnNumber: Number(row.columnNumber),
    lineNumber: Number(row.lineNumber),
    currentTime: Number(row.currentTime)
  }
  const res = await isGetRadio(data).catch(()=>{})
  if(res){
      reader.readAsText(res)
      reader.onload = function () {
        let events = JSON.parse(atou(this.result))
        if(events.length > 2){
          new rrwebPlayer(
            {
              target: document.getElementById('player'),
              data: {events}
            }
          )
        }
      }
  }
}
const atou = (binary:string) => {
    // 将字符串转成Uint8Array
    const buffer = strToU8(binary, true)
    // 解压缩
    const unzipped = unzlibSync(buffer)
    // 将Uint8Array重新转换成字符串
    return strFromU8(unzipped)
}
// 初始化websocket
const connectSocket = () => {
  if(ws){ 
    ws.close()  // 每次创建一次webSocket对象,如果已经有了,都要关闭并清空
    ws = null 
  }
  ws = new WebSocket("ws://localhost:8080/socket");
  ws.onopen = function (e:Event) {
    ws.send("客户端收到,连接成功")
    clearInterval(timeID)
    timeID = null
  }
  ws.onclose = function (e:Event) {
    if(!timeID){
      timeID = setInterval(function () {
        connectSocket()
      }, 1500)
    }
  }
  ws.onmessage = function (e:MessageEvent) {
    if (e.data) {
      let { result, data } = JSON.parse(e.data)
      console.log(data, '服务器发送过来了')
      if(result === 1){
        if(Object.keys(data).length){
          for (let key in data) {
            let needObj = allTable.value.find(item => item.typeEnglishName === key)
            if(needObj){
              for (let i = 0; i < data[key].length; i++) {
                data[key][i].currentTime = dayjs(Number(data[key][i].currentTime)).format('YYYY-MM-DD hh:MM:ss')
              }
              needObj.tableData = data[key]
            }
          }
        }else{
          allTable.value = JSON.parse(JSON.stringify(params))
        }
      }
    }
  }
}

</script>
  
<style scoped>
  .page {padding: 24px 50px;}
  .tableBac {margin-bottom: 40px;}
  .pageTitle {font-weight: bold;margin-bottom: 14px;}
  .dialogBox > :deep(.el-dialog__body){padding: 0!important;}
  .codeContentBox{display: flex;}
  .codeContentBox_left{width: 50px;border-right: 1px solid #cfd2d5;text-align: right;padding: 0 10px;margin-right: 10px;}
  .codeContentBox_right{flex: 1;}
  :deep(.inputBoxB){width: 100%;pointer-events: none;background-color: #ffc784;color: red;}
  :deep(.inputBox){width: 100%;pointer-events: none;}
  .pageTop{font-weight: bold;padding: 0 20px 20px 20px;font-size: 16px;border-bottom: 1px solid #cfd2d5;}
  :deep(.el-dialog__title){font-weight: bold;}
  .el-dialog{border-radius: 10px;}
</style>